package com.a1anwang.okble.client.core;

import com.a1anwang.okble.client.scan.BLEScanResult;
import com.a1anwang.okble.common.*;
import ohos.app.Context;
import ohos.bluetooth.BluetoothRemoteDevice;
import ohos.bluetooth.ProfileBase;
import ohos.bluetooth.ble.*;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.*;

import static com.a1anwang.okble.client.core.OKBLEOperation.BaseOperationListener.Operation_FAILED_BLE_Failed;
import static com.a1anwang.okble.client.core.OKBLEOperation.BaseOperationListener.Operation_FAILED_Overtime;
import static com.a1anwang.okble.client.core.OKBLEOperation.OperationType.OperationType_Read;


/**
 * Created by a1anwang.com on 2017/6/26.
 * contact：www.a1anwang.com
 */

public class OKBLEDeviceImp implements OKBLEDevice {
    private String TAG = "OKBLE";
    private EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
    private BlePeripheralDevice mBluetoothGatt;
    private BLEScanResult bleScanResult;
    private BLEOperationQueue<OKBLEOperation> bleOperationQueue;
    private List<GattService> bluetoothGattServices;
    private HashMap<String, GattCharacteristic> characteristicHashMap = new HashMap<>();
    private List<OKBLEDeviceListener> OKBLEDeviceListeners;
    private static final int Default_MTU = 23;
    private BluetoothRemoteDevice bluetoothDevice;
    private String deviceTAG = "";
    /**
     * 操作超时时间
     */
    private int OperationOverTime = Default_OperationOverTime;

    /**
     * 操作间隔时间
     */
    private int OperationInterval = 30;

    /**
     * 是否需要在连接断开的时候自动重连
     */
    private boolean autoReconnect;
    private DeviceStatus deviceStatus = DeviceStatus.DEVICE_STATUS_INITIAL;

    @Override
    public DeviceStatus getDeviceStatus() {
        return deviceStatus;
    }


    @Override
    public void addDeviceListener(OKBLEDeviceListener OKBLEDeviceListener) {
        if (OKBLEDeviceListeners == null) {
            OKBLEDeviceListeners = new ArrayList<>();
        }
        if (!OKBLEDeviceListeners.contains(OKBLEDeviceListener)) {
            OKBLEDeviceListeners.add(OKBLEDeviceListener);
        }
    }

    @Override
    public void removeDeviceListener(OKBLEDeviceListener OKBLEDeviceListener) {
        if (OKBLEDeviceListeners != null) {
            if (OKBLEDeviceListener != null) {
                OKBLEDeviceListeners.remove(OKBLEDeviceListener);
            }
        }
    }

    @Override
    public void clearDeviceListener() {
        if (OKBLEDeviceListeners != null) {
            OKBLEDeviceListeners.clear();
        }
    }

    @Override
    public void clearOperations() {
        if (bleOperationQueue != null) {
            bleOperationQueue.clear();
        }
    }

    /**
     * 获取mac地址
     *
     * @return string
     */
    public String getMacAddress() {
        if (this.bluetoothDevice != null) {
            return this.bluetoothDevice.getDeviceAddr();
        }
        return null;
    }

    @Override
    public void setDeviceTAG(String deviceTAG) {
        this.deviceTAG = deviceTAG;
    }

    @Override
    public String getDeviceTAG() {
        return deviceTAG;
    }


    @Override
    public BluetoothRemoteDevice getBluetoothDevice() {
        return this.bluetoothDevice;
    }

    /**
     * 构造函数
     *
     * @param context 上下文
     */
    public OKBLEDeviceImp(Context context) {
        bleOperationQueue = new BLEOperationQueue<>();
    }

    /**
     * 构造函数
     *
     * @param context       上下文
     * @param bleScanResult 扫描结果
     */
    public OKBLEDeviceImp(Context context, BLEScanResult bleScanResult) {
        this.bleScanResult = bleScanResult;
        this.bluetoothDevice = bleScanResult.getBluetoothDevice();
        if (deviceTAG == null || "".equals(deviceTAG)) {
            this.deviceTAG = getMacAddress();
        }
        deviceStatus = DeviceStatus.DEVICE_STATUS_DISCONNECTED;

        bleOperationQueue = new BLEOperationQueue<>();
    }


    @Override
    public synchronized void setOperationOverTime(int overTime) {
        this.OperationOverTime = overTime;
    }

    @Override
    public void setOperationInterval(int interval) {
        this.OperationInterval = interval;
    }


    @Override
    public void setBleScanResult(BLEScanResult scanResult) {
        reSet();
        this.bleScanResult = scanResult;
        this.bluetoothDevice = bleScanResult.getBluetoothDevice();
        if (deviceTAG == null || deviceTAG.equals("")) {
            deviceTAG = getMacAddress();
        }
        deviceStatus = DeviceStatus.DEVICE_STATUS_DISCONNECTED;
    }


    @Override
    public void setBluetoothDevice(BluetoothRemoteDevice bluetoothDevice) {
        reSet();
        this.bluetoothDevice = bluetoothDevice;
        if (deviceTAG == null || "".equals(deviceTAG)) {
            deviceTAG = getMacAddress();
        }
        deviceStatus = DeviceStatus.DEVICE_STATUS_DISCONNECTED;

    }

    @Override
    public boolean disConnect(boolean autoReconnect) {
        this.autoReconnect = autoReconnect;
        if (!autoReconnect) {
            handler.removeTask(connectGattRunnable);
        }
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
            return true;
        }
        return false;
    }

    /**
     * 判断连接状态
     *
     * @return boolean
     */
    public boolean isConnected() {
        if (deviceStatus == DeviceStatus.DEVICE_STATUS_CONNECTED) {
            return true;
        }
        return false;
    }

    @Override
    public boolean connect(boolean autoReconnect) {
        this.autoReconnect = autoReconnect;
        if (deviceStatus == DeviceStatus.DEVICE_STATUS_CONNECTED) {
            return true;
        }
        if (deviceStatus == DeviceStatus.DEVICE_STATUS_CONNECTING) {
            return true;
        }
        return doConnect();
    }


    private boolean doConnect() {
        if (bluetoothDevice != null) {
            deviceStatus = DeviceStatus.DEVICE_STATUS_CONNECTING;
            if (mBluetoothGatt != null) {
                refreshGatt(mBluetoothGatt);
            }
            handler.removeTask(connectGattRunnable);
            handler.postTask(connectGattRunnable, 300);
            return true;
        } else {
            LogUtils.e("the bluetoothDevice is null, please reset the bluetoothDevice");
            return false;
        }
    }

    private void doReconnect() {
        handler.removeTask(connectGattRunnable);
        handler.postTask(connectGattRunnable, 300);
    }

    private void connectComplete() {
        handler.removeTask(connectGattRunnable);
    }

    Runnable connectGattRunnable = new Runnable() {

        @Override
        public void run() {
            if (bluetoothDevice != null) {
                reSet();

                mBluetoothGatt = BlePeripheralDevice.createInstance(bluetoothDevice.getDeviceAddr());
                mBluetoothGatt.connect(false, gattCallback);

                LogUtils.e("----connectGatt-----");
                if (autoReconnect) {
                    handler.postTask(this, 30 * 1000);
                }
            }
        }
    };


    private boolean refreshGatt(BlePeripheralDevice gatt) {
        BlePeripheralDevice localGatt = gatt;
        try {
            Method localMethod = localGatt.getClass().getMethod("refresh",
                    new Class[0]);
            if (localMethod != null) {
                boolean result = ((Boolean) localMethod.invoke(localGatt,
                        new Object[0])).booleanValue();
                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public List<OKBLEServiceModel> getServiceModels() {
        if (!isConnected()) {
            return null;
        }
        if (bluetoothGattServices == null || bluetoothGattServices.size() <= 0) {
            bluetoothGattServices = mBluetoothGatt.getServices();
        }
        if (bluetoothGattServices == null || bluetoothGattServices.size() <= 0) {
            return null;
        }
        List<OKBLEServiceModel> okbleServiceModels = new ArrayList<>();
        for (GattService service : bluetoothGattServices) {
            OKBLEServiceModel serviceModel = new OKBLEServiceModel(service.getUuid().toString());
            serviceModel.setDesc(CommonUUIDUtils.getUUIDDesc(serviceModel.getUuid()));
            for (GattCharacteristic characteristic : service.getCharacteristics()) {
                OKBLECharacteristicModel characteristicModel = new OKBLECharacteristicModel(
                        characteristic.getUuid().toString());
                characteristicModel.setDesc(CommonUUIDUtils.getUUIDDesc(characteristicModel.getUuid()));

                if ((characteristic.getProperties() & GattCharacteristic.PROPERTY_WRITE) != 0) {
                    characteristicModel.setCanWrite(true);
                }
                if ((characteristic.getProperties() & GattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) {
                    characteristicModel.setCanWriteNoResponse(true);
                }
                if ((characteristic.getProperties() & GattCharacteristic.PROPERTY_READ) != 0) {
                    characteristicModel.setCanRead(true);
                }
                if ((characteristic.getProperties() & 16) != 0) {
                    characteristicModel.setCanNotify(true);
                }
                if ((characteristic.getProperties() & 32) != 0) {
                    characteristicModel.setCanIndicate(true);
                }
                serviceModel.addCharacteristicModel(characteristicModel);
            }
            okbleServiceModels.add(serviceModel);
        }
        return okbleServiceModels;
    }


    // 实现外围设备操作回调
    private BlePeripheralCallback gattCallback = new BlePeripheralCallback() {
        @Override
        public void servicesDiscoveredEvent(int status) {
            super.servicesDiscoveredEvent(status);
            bleOperationQueue.clear();
            handler.removeTask(operationOverTimeRunnable);

            if (status == BlePeripheralDevice.OPERATION_SUCC) {
                deviceStatus = DeviceStatus.DEVICE_STATUS_CONNECTED;
                connectComplete();
                bluetoothGattServices = mBluetoothGatt.getServices();
                if (OKBLEDeviceListeners != null) {
                    for (OKBLEDeviceListener listener : OKBLEDeviceListeners) {
                        listener.onConnected(deviceTAG);
                    }
                }

            }
        }

        @Override
        public void connectionStateChangeEvent(int status) {
            super.connectionStateChangeEvent(status);
            LogUtils.e(TAG, " onConnectionStateChange status:" + status);
            bleOperationQueue.clear();
            handler.removeTask(operationOverTimeRunnable);
            if (status == ProfileBase.STATE_CONNECTED) {
                mBluetoothGatt.discoverServices();
            } else if (status == ProfileBase.STATE_DISCONNECTED) {
                deviceStatus = DeviceStatus.DEVICE_STATUS_DISCONNECTED;
                reSet();
                if (OKBLEDeviceListeners != null) {
                    LogUtils.e(" OKBLEDeviceListeners size:" + OKBLEDeviceListeners.size());
                    for (OKBLEDeviceListener listener : OKBLEDeviceListeners) {
                        listener.onDisconnected(deviceTAG);
                    }
                }
                if (autoReconnect) {
                    doReconnect();
                }
            }
        }

        @Override
        public void characteristicReadEvent(GattCharacteristic characteristic, int status) {
            super.characteristicReadEvent(characteristic, status);
            LogUtils.e(TAG, " onCharacteristicRead status:" + status + " characteristic:" +
                    characteristic.getUuid().toString() + " value:" +
                    OKBLEDataUtils.BytesToHexString(characteristic.getValue()));
            handler.removeTask(operationOverTimeRunnable);
            if (status == BlePeripheralDevice.OPERATION_SUCC) {
                if (characteristic.getUuid().toString().equals(CommonUUIDUtils.Battery_Level)) {
                    if (OKBLEDeviceListeners != null) {
                        for (OKBLEDeviceListener listener : OKBLEDeviceListeners) {
                            listener.onReadBattery(deviceTAG, characteristic.getValue()[0]);
                        }
                    }
                }
            }
            if (OKBLEDeviceListeners != null) {
                for (OKBLEDeviceListener listener : OKBLEDeviceListeners) {
                    listener.onReadValue(deviceTAG, characteristic.getUuid().toString(), characteristic.getValue(),
                            status == BlePeripheralDevice.OPERATION_SUCC);
                }
            }

            if (!bleOperationQueue.isEmpty()) {
                OKBLEOperation operation = bleOperationQueue.removeFirst();
                if (operation != null && operation.operationListener != null) {
                    if (status == BlePeripheralDevice.OPERATION_SUCC) {
                        if (operation.operationListener instanceof OKBLEOperation.ReadOperationListener) {
                            ((OKBLEOperation.ReadOperationListener) operation.operationListener)
                                    .onReadValue(characteristic.getValue());
                        }
                    } else {
                        operation.operationListener.onFail(Operation_FAILED_BLE_Failed,
                                OKBLEOperationFailedDescUtils.getDesc(status));
                    }
                }
                if (!bleOperationQueue.isEmpty()) {
                    handler.removeTask(nextRunnable);
                    handler.postTask(nextRunnable, OperationInterval);
                }
            }
        }

        @Override
        public void characteristicWriteEvent(GattCharacteristic characteristic, int status) {
            super.characteristicWriteEvent(characteristic, status);
            handler.removeTask(operationOverTimeRunnable);
            LogUtils.e(TAG, " onCharacteristicWrite status:" + status + " characteristic:" +
                    characteristic.getUuid().toString());
            if (OKBLEDeviceListeners != null) {
                for (OKBLEDeviceListener listener : OKBLEDeviceListeners) {
                    listener.onWriteValue(deviceTAG, characteristic.getUuid().toString(), characteristic.getValue(),
                            status == BlePeripheralDevice.OPERATION_SUCC);
                }
            }
            if (!bleOperationQueue.isEmpty()) {
                OKBLEOperation operation = bleOperationQueue.removeFirst();
                if (operation != null && operation.operationListener != null) {
                    if (status == BlePeripheralDevice.OPERATION_SUCC) {
                        if (operation.operationListener instanceof OKBLEOperation.WriteOperationListener) {
                            ((OKBLEOperation.WriteOperationListener) operation.operationListener)
                                    .onWriteValue(characteristic.getValue());
                        }
                    } else {
                        operation.operationListener.onFail(Operation_FAILED_BLE_Failed,
                                OKBLEOperationFailedDescUtils.getDesc(status));
                    }
                }
                if (!bleOperationQueue.isEmpty()) {
                    handler.removeTask(nextRunnable);
                    handler.postTask(nextRunnable, OperationInterval);
                }
            }
        }

        @Override
        public void characteristicChangedEvent(GattCharacteristic characteristic) {
            super.characteristicChangedEvent(characteristic);
            LogUtils.e(TAG, " onCharacteristicChanged characteristic:" + characteristic.getUuid().toString() +
                    " value:" + OKBLEDataUtils.BytesToHexString(characteristic.getValue()));
            if (OKBLEDeviceListeners != null) {
                for (OKBLEDeviceListener listener : OKBLEDeviceListeners) {
                    listener.onReceivedValue(deviceTAG, characteristic.getUuid().toString(), characteristic.getValue());
                }
            }
        }

        @Override
        public void descriptorReadEvent(GattDescriptor descriptor, int status) {
            super.descriptorReadEvent(descriptor, status);
        }

        @Override
        public void descriptorWriteEvent(GattDescriptor descriptor, int status) {
            super.descriptorWriteEvent(descriptor, status);
            LogUtils.e(TAG, " onDescriptorWrite:" + status + " descriptor:" + descriptor.getUuid().toString() +
                    " char:" + descriptor.getCharacteristic().getUuid().toString());
            handler.removeTask(operationOverTimeRunnable);
            if (!bleOperationQueue.isEmpty()) {
                OKBLEOperation operation = bleOperationQueue.removeFirst();
                if (operation != null && operation.operationListener != null) {
                    if (status == BlePeripheralDevice.OPERATION_SUCC) {
                        if (operation.operationListener instanceof OKBLEOperation.NotifyOrIndicateOperationListener) {
                            ((OKBLEOperation.NotifyOrIndicateOperationListener) operation.operationListener)
                                    .onNotifyOrIndicateComplete();
                        }
                    } else {
                        operation.operationListener.onFail(Operation_FAILED_BLE_Failed,
                                OKBLEOperationFailedDescUtils.getDesc(status));
                    }
                }
                if (!bleOperationQueue.isEmpty()) {
                    handler.removeTask(nextRunnable);
                    handler.postTask(nextRunnable, OperationInterval);
                }
            }
        }

        @Override
        public void readRemoteRssiEvent(int rssi, int ret) {
            super.readRemoteRssiEvent(rssi, ret);
        }

        @Override
        public void mtuUpdateEvent(int mtu, int status) {
            super.mtuUpdateEvent(mtu, status);
            LogUtils.e("onMtuChanged mtu:" + mtu);
            handler.removeTask(operationOverTimeRunnable);
            if (!bleOperationQueue.isEmpty()) {
                OKBLEOperation operation = bleOperationQueue.removeFirst();
                if (operation != null && operation.operationListener != null) {
                    if (status == BlePeripheralDevice.OPERATION_SUCC) {
                        if (operation.operationListener instanceof OKBLEOperation.ChangeMTUListener) {
                            ((OKBLEOperation.ChangeMTUListener) operation.operationListener).onMtuChange(mtu);
                        }
                    } else {
                        operation.operationListener.onFail(Operation_FAILED_BLE_Failed,
                                OKBLEOperationFailedDescUtils.getDesc(status));
                    }
                }
                if (!bleOperationQueue.isEmpty()) {
                    handler.removeTask(nextRunnable);
                    handler.postTask(nextRunnable, OperationInterval);
                }
            }
        }
    };

    Runnable nextRunnable = new Runnable() {
        @Override
        public void run() {
            if (!bleOperationQueue.isEmpty()) {
                doNextBleOperation();
            }
        }
    };


    private void reSet() {
        if (deviceTAG != null && bluetoothDevice != null && deviceTAG.equals(getMacAddress())) {
            deviceTAG = "";
        }
        bleOperationQueue.clear();
        handler.removeTask(nextRunnable);
        handler.removeTask(connectGattRunnable);
        if (bluetoothGattServices != null) {
            bluetoothGattServices.clear();
        }
        characteristicHashMap.clear();
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
            mBluetoothGatt.close();
            refreshGatt(mBluetoothGatt);
            mBluetoothGatt = null;
        }
    }

    /**
     * 移除操作
     */
    public void remove() {
        handler.removeTask(connectGattRunnable);
        if (mBluetoothGatt != null) {
            mBluetoothGatt.close();
            mBluetoothGatt.disconnect();
            refreshGatt(mBluetoothGatt);
            mBluetoothGatt = null;
        }
        reSet();
        deviceStatus = DeviceStatus.DEVICE_STATUS_INITIAL;
        bleScanResult = null;
        bluetoothDevice = null;

    }

    @Override
    public void addChangeMTUOperation(int mtu, OKBLEOperation.ChangeMTUListener changeMTUListener) {
        if (!isConnected()) {
            String errMsg = "change mtu failed, device not connected";
            LogUtils.e(TAG, errMsg);
            if (changeMTUListener != null) {
                changeMTUListener.onFail(OKBLEOperation.BaseOperationListener
                        .Operation_FAILED_Device_Not_Connected, errMsg);
            }
            return;
        }
        if (mtu < Default_MTU) {
            String errMsg = "change mtu failed, mtu invalid";
            LogUtils.e(TAG, errMsg);
            if (changeMTUListener != null) {
                changeMTUListener.onFail(OKBLEOperation.BaseOperationListener
                        .Operation_FAILED_Device_Not_Connected, errMsg);
            }
            return;
        }
        OKBLEOperation okbleOperation = new OKBLEOperation();
        okbleOperation.mtu = mtu;
        okbleOperation.operationType = OKBLEOperation.OperationType.OperationType_Change_MTU;
        okbleOperation.operationListener = changeMTUListener;
        bleOperationQueue.add(okbleOperation);
        checkNextBleOperation();
    }

    /**
     * 写操作
     *
     * @param characteristicUUID uuid
     * @param value 值
     * @param listener 监听
     */
    public void addWriteOperation(String characteristicUUID, String value,
                                  OKBLEOperation.WriteOperationListener listener) {
        addWriteOperation(characteristicUUID, OKBLEDataUtils.hexStringToBytes(value), listener);
    }

    /**
     * 写操作
     *
     * @param characteristicUUID uuid
     * @param value 值
     * @param listener 监听
     */
    public void addWriteOperation(String characteristicUUID, byte[] value,
                                  OKBLEOperation.WriteOperationListener listener) {
        if (!isConnected()) {
            String errMsg = "addWriteOperation failed, device not connected";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Device_Not_Connected, errMsg);
            }
            return;
        }
        if (value == null) {
            String errMsg = "addWriteOperation failed, value is null";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Null_Value, errMsg);
            }
            return;
        }
        characteristicUUID = characteristicUUID.trim().toLowerCase();
        if (!OKBLEDataUtils.isValidUUID(characteristicUUID) && characteristicUUID.length() != 4) {
            String errMsg = "characteristicUUID not valid";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Invalid_UUID, errMsg);
            }
            return;
        }
        GattCharacteristic characteristic = characteristicHashMap.get(characteristicUUID);
        if (characteristic == null) {
            characteristic = findCharacteristic(characteristicUUID);
            if (characteristic != null) {
                characteristicHashMap.put(characteristicUUID, characteristic);
            }
        }
        if (characteristic != null) {
            if ((characteristic.getProperties() & GattCharacteristic.PROPERTY_WRITE) != 0) {
                OKBLEOperation okbleOperation = new OKBLEOperation();
                okbleOperation.operationType = OKBLEOperation.OperationType.OperationType_Write;
                okbleOperation.bleChar = characteristic;
                okbleOperation.value = value;
                okbleOperation.operationListener = listener;
                bleOperationQueue.add(okbleOperation);
                checkNextBleOperation();
            } else if ((characteristic.getProperties() & GattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) {
                OKBLEOperation okbleOperation = new OKBLEOperation();
                okbleOperation.operationType = OKBLEOperation.OperationType.OperationType_Write_No_Response;
                okbleOperation.bleChar = characteristic;
                okbleOperation.value = value;
                okbleOperation.operationListener = listener;
                bleOperationQueue.add(okbleOperation);
                checkNextBleOperation();
            } else {
                String errMsg = "addWriteOperation failed, write property not found";
                LogUtils.e(TAG, errMsg);
                if (listener != null) {
                    listener.onFail(OKBLEOperation.BaseOperationListener
                            .Operation_FAILED_Characteristic_Property_Not_Found, errMsg);
                }
                return;
            }
        } else {
            String errMsg = "addWriteOperation failed, characteristic not found";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Characteristic_Not_Found, errMsg);
            }
            return;
        }
    }

    /**
     * 读操作
     *
     * @param characteristicUUID uuid
     * @param listener 监听
     */
    public void addReadOperation(String characteristicUUID, OKBLEOperation.ReadOperationListener listener) {
        if (!isConnected()) {
            String errMsg = "addReadOperation failed, device not connected";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Device_Not_Connected, errMsg);
            }
            return;
        }
        characteristicUUID = characteristicUUID.trim().toLowerCase();
        if (!OKBLEDataUtils.isValidUUID(characteristicUUID) && characteristicUUID.length() != 4) {
            String errMsg = "characteristicUUID not valid ";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Invalid_UUID, errMsg);
            }
            return;
        }
        GattCharacteristic characteristic = characteristicHashMap.get(characteristicUUID);
        if (characteristic == null) {
            characteristic = findCharacteristic(characteristicUUID);
            if (characteristic != null) {
                characteristicHashMap.put(characteristicUUID, characteristic);
            }
        }
        if (characteristic != null) {
            if ((characteristic.getProperties() & GattCharacteristic.PROPERTY_READ) != 0) {
                OKBLEOperation okbleOperation = new OKBLEOperation();
                okbleOperation.operationType = OperationType_Read;
                okbleOperation.bleChar = characteristic;
                okbleOperation.operationListener = listener;
                bleOperationQueue.add(okbleOperation);
                checkNextBleOperation();
            } else {
                String errMsg = "addReadOperation failed, read property not found";
                LogUtils.e(TAG, errMsg);
                if (listener != null) {
                    listener.onFail(OKBLEOperation.BaseOperationListener
                            .Operation_FAILED_Characteristic_Property_Not_Found, errMsg);
                }
                return;
            }
        } else {
            String errMsg = "addReadOperation failed, characteristic not found";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Characteristic_Not_Found, errMsg);
            }
            return;
        }
    }

    /**
     * 通知或者指示
     *
     * @param characteristicUUID uuid
     * @param enable             布尔类型  标识状态
     * @param listener           监听
     */
    public void addNotifyOrIndicateOperation(String characteristicUUID, boolean enable,
                                             OKBLEOperation.NotifyOrIndicateOperationListener listener) {
        if (!isConnected()) {
            String errMsg = "addNotifyOrIndicateOperation failed, device not connected";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Device_Not_Connected, errMsg);
            }
            return;
        }
        characteristicUUID = characteristicUUID.trim().toLowerCase();
        if (!OKBLEDataUtils.isValidUUID(characteristicUUID) && characteristicUUID.length() != 4) {
            String errMsg = "characteristicUUID not valid ";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Invalid_UUID, errMsg);
            }
            return;
        }
        GattCharacteristic characteristic = characteristicHashMap.get(characteristicUUID);
        if (characteristic == null) {
            characteristic = findCharacteristic(characteristicUUID);
            if (characteristic != null) {
                characteristicHashMap.put(characteristicUUID, characteristic);
            }
        }
        if (characteristic != null) {
            if ((characteristic.getProperties() & 0x10) != 0) {
                OKBLEOperation okbleOperation = new OKBLEOperation();
                if (enable) {
                    okbleOperation.operationType = enable ? OKBLEOperation.OperationType.OperationType_Enable_Notify
                            : OKBLEOperation.OperationType.OperationType_Disable_Notify;
                }
                okbleOperation.bleChar = characteristic;
                okbleOperation.operationListener = listener;
                bleOperationQueue.add(okbleOperation);
                checkNextBleOperation();
            } else if ((characteristic.getProperties() & 0x20) != 0) {
                OKBLEOperation okbleOperation = new OKBLEOperation();
                if (enable) {
                    okbleOperation.operationType = enable ? OKBLEOperation.OperationType.OperationType_Enable_Indicate
                            : OKBLEOperation.OperationType.OperationType_Disable_Indicate;
                }
                okbleOperation.bleChar = characteristic;
                okbleOperation.operationListener = listener;
                bleOperationQueue.add(okbleOperation);
                checkNextBleOperation();
            } else {
                String errMsg = "addNotifyOrIndicateOperation failed, Notify or Indicate property not found";
                LogUtils.e(TAG, errMsg);
                if (listener != null) {
                    listener.onFail(OKBLEOperation.BaseOperationListener
                            .Operation_FAILED_Characteristic_Property_Not_Found, errMsg);
                }
                return;
            }
        } else {
            String errMsg = "addNotifyOrIndicateOperation failed, characteristic not found";
            LogUtils.e(TAG, errMsg);
            if (listener != null) {
                listener.onFail(OKBLEOperation.BaseOperationListener.Operation_FAILED_Characteristic_Not_Found, errMsg);
            }
        }
    }

    private GattCharacteristic findCharacteristic(String uuid) {
        if (bluetoothGattServices == null || bluetoothGattServices.size() == 0) {
            return null;
        }
        uuid = uuid.toLowerCase();
        String entireUUID = uuid;
        if (entireUUID.length() == 4) {
            entireUUID = CommonUUIDUtils.CommonUUIDStr_x.replace("xxxx", uuid);
        }
        LogUtils.e(TAG, " entireUUID:" + entireUUID);
        for (GattService server : bluetoothGattServices) {
            for (GattCharacteristic characteristic : server.getCharacteristics()) {
                if (characteristic.getUuid().toString().equals(entireUUID)) {
                    return characteristic;
                }
            }
        }
        return null;
    }

    private synchronized void checkNextBleOperation() {
        if (bleOperationQueue.getOperationSize() == 1) {
            OKBLEOperation okbleOperation = bleOperationQueue.getFirst();
            doBleOperation(okbleOperation);
        }
    }

    private synchronized void doBleOperation(OKBLEOperation okbleOperation) {
        LogUtils.e(TAG, " BleOperation size:" + bleOperationQueue.getOperationSize());
        if (!isConnected()) {
            LogUtils.e(TAG, "doBleOperation failed, device not connected");
            return;
        }
        switch (okbleOperation.operationType) {
            case OperationType_Read: {
                LogUtils.e(TAG, " read:" + okbleOperation.bleChar.getUuid().toString());
                boolean success = readCharacteristic(okbleOperation.bleChar);
                if (success) {
                    handler.removeTask(operationOverTimeRunnable);
                    handler.postTask(operationOverTimeRunnable, OperationOverTime);
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onExecuteSuccess(okbleOperation.operationType);
                    }
                } else {
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onFail(Operation_FAILED_BLE_Failed, "BLE_Failed");
                    }
                    bleOperationQueue.removeFirst();
                    doNextBleOperation();
                }
                break;
            }
            case OperationType_Enable_Notify:
            case OperationType_Enable_Indicate: {
                LogUtils.e(TAG, " enableNotification/Indication:"
                        + okbleOperation.bleChar.getUuid().toString());

                boolean success = setNotificationOrIndication(true, okbleOperation.bleChar);
                if (success) {
                    handler.removeTask(operationOverTimeRunnable);
                    handler.postTask(operationOverTimeRunnable, OperationOverTime);
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onExecuteSuccess(okbleOperation.operationType);
                    }
                } else {
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onFail(Operation_FAILED_BLE_Failed, "BLE_Failed");
                    }
                    bleOperationQueue.removeFirst();
                    doNextBleOperation();
                }
                break;
            }
            case OperationType_Disable_Notify:
            case OperationType_Disable_Indicate: {
                LogUtils.e(TAG, " disableNotification/Indication:"
                        + okbleOperation.bleChar.getUuid().toString());
                boolean success = setNotificationOrIndication(false, okbleOperation.bleChar);
                if (success) {
                    handler.removeTask(operationOverTimeRunnable);
                    handler.postTask(operationOverTimeRunnable, OperationOverTime);
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onExecuteSuccess(okbleOperation.operationType);
                    }
                } else {
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onFail(Operation_FAILED_BLE_Failed, "BLE_Failed");
                    }
                    bleOperationQueue.removeFirst();
                    doNextBleOperation();
                }
                break;
            }
            case OperationType_Write_No_Response:
            case OperationType_Write: {
                byte[] values = okbleOperation.value;
                boolean success = writeCharacteristic(okbleOperation.bleChar, values);
                if (success) {
                    handler.removeTask(operationOverTimeRunnable);
                    handler.postTask(operationOverTimeRunnable, OperationOverTime);
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onExecuteSuccess(okbleOperation.operationType);
                    }
                } else {
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onFail(Operation_FAILED_BLE_Failed, "BLE_Failed");
                    }
                    bleOperationQueue.removeFirst();
                    doNextBleOperation();
                }
                break;
            }
            case OperationType_Change_MTU:
                LogUtils.e(TAG, " requestMtu :" + okbleOperation.mtu);
                boolean success = mBluetoothGatt.requestBleMtuSize(okbleOperation.mtu);
                if (success) {
                    handler.removeTask(operationOverTimeRunnable);
                    handler.postTask(operationOverTimeRunnable, OperationOverTime);
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onExecuteSuccess(okbleOperation.operationType);
                    }
                } else {
                    if (okbleOperation.operationListener != null) {
                        okbleOperation.operationListener.onFail(Operation_FAILED_BLE_Failed, "BLE_Failed");
                    }
                    bleOperationQueue.removeFirst();
                    doNextBleOperation();
                }
                break;
            default:
                break;
        }
    }

    private Runnable operationOverTimeRunnable = new Runnable() {

        @Override
        public void run() {
            if (!bleOperationQueue.isEmpty()) {
                OKBLEOperation operation = bleOperationQueue.removeFirst();
                if (operation != null && operation.operationListener != null) {
                    operation.operationListener.onFail(Operation_FAILED_Overtime, "failed,Overtime");
                }
                doNextBleOperation();
            }
        }
    };

    private void doNextBleOperation() {
        if (!bleOperationQueue.isEmpty()) {
            OKBLEOperation okbleOperation = bleOperationQueue.getFirst();

            doBleOperation(okbleOperation);
        }
    }

    private boolean readCharacteristic(
            GattCharacteristic characteristic) {
        if (!isConnected()) {
            LogUtils.e(TAG, "readCharacteristic failed, device not connected");
            return false;
        }
        if (mBluetoothGatt == null) {
            LogUtils.e(TAG, "readCharacteristic failed, mBluetoothGatt is null");
            return false;
        }
        if (characteristic == null) {
            LogUtils.e(TAG, "readCharacteristic failed, characteristic is null");
            return false;
        }
        boolean bl = mBluetoothGatt.readCharacteristic(characteristic);
        LogUtils.e(TAG, " readCharacteristic " + bl);
        return bl;
    }

    private boolean writeCharacteristic(GattCharacteristic characteristic, byte[] values) {
        if (!isConnected()) {
            LogUtils.e(TAG, "writeCharacteristic failed, device not connected");
            return false;
        }
        if (mBluetoothGatt == null) {
            LogUtils.e(TAG, "writeCharacteristic failed, mBluetoothGatt is null");
            return false;
        }
        if (characteristic == null) {
            LogUtils.e(TAG, "writeCharacteristic failed, characteristic is null");
            return false;
        }
        characteristic.setValue(values);
        boolean bl = mBluetoothGatt.writeCharacteristic(characteristic);
        LogUtils.e(TAG, " writeCharacteristic " + bl + ", value:" + OKBLEDataUtils.BytesToHexString(values));
        return bl;
    }

    private boolean setNotificationOrIndication(boolean enable,
                                                GattCharacteristic characteristic) {
        if (characteristic == null) {
            LogUtils.e(TAG, "setNotificationOrIndication failed, characteristic is null");
            return false;
        }
        if ((characteristic.getProperties() & 0x10) == 0
                && (characteristic.getProperties() & 0x20) == 0) {
            LogUtils.e(TAG, "setNotificationOrIndication failed, "
                    + "characteristic has no notification or indication function");
            return false;
        }
        if (!isConnected()) {
            LogUtils.e(TAG, "setNotificationOrIndication failed, device not connected");
            return false;
        }
        if (mBluetoothGatt == null) {
            LogUtils.e(TAG, "setNotificationOrIndication failed, mBluetoothGatt is null");
            return false;
        }
        if (!mBluetoothGatt.setNotifyCharacteristic(characteristic,
                enable)) {
            LogUtils.e(TAG, "setNotificationOrIndication failed");
            return false;
        }
        GattDescriptor clientConfig = null;
        Optional<GattDescriptor> descriptor = characteristic.getDescriptor(
                UUID.fromString(CommonUUIDUtils.Client_Characteristic_Configuration));

        if (descriptor.isPresent()) {
            clientConfig = descriptor.get();
        }
        if (clientConfig == null) {
            LogUtils.e(TAG, "setNotificationOrIndication failed,clientConfig is null");
            return false;
        }
        byte[] configValue = null;
        if (enable) {
            if ((characteristic.getProperties() & 0x10) != 0) {
                configValue = new byte[]{0x01, 0x00};
            } else if ((characteristic.getProperties() & 0x20) != 0) {
                configValue = new byte[]{0x02, 0x00};
            }
        } else {
            configValue = new byte[]{0x00, 0x00};
        }
        boolean bl = clientConfig.setValue(configValue);
        if (!bl) {
            LogUtils.e(TAG, "setNotificationOrIndication failed,clientConfig setValue failed");
            return false;
        }
        bl = mBluetoothGatt.writeDescriptor(clientConfig);
        LogUtils.e(TAG, "setNotificationOrIndication:" + bl);
        return bl;
    }

    /**
     * 判断Notify状态
     *
     * @param uuid uuid
     * @return boolean
     */
    public boolean isNotifyEnabled(String uuid) {
        GattCharacteristic characteristic = findCharacteristic(uuid);
        if (characteristic == null) {
            LogUtils.e(TAG, "characteristic not found");
            return false;
        }
        if ((characteristic.getProperties() & 0x10) != 0) {
            GattDescriptor descriptor = characteristic.getDescriptor(
                    UUID.fromString(CommonUUIDUtils.Client_Characteristic_Configuration)).get();
            if (descriptor != null) {
                byte[] value = descriptor.getValue();
                if (value != null) {
                    if (Arrays.equals(value, new byte[]{0x01, 0x00})) {
                        return true;
                    } else {
                        return false;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 判断Indicate状态
     *
     * @param uuid uuid
     * @return boolean
     */
    public boolean isIndicateEnabled(String uuid) {
        GattCharacteristic characteristic = findCharacteristic(uuid);
        if (characteristic == null) {
            LogUtils.e(TAG, "characteristic not found");

            return false;
        }
        if ((characteristic.getProperties() & 0x20) != 0) {
            Optional<GattDescriptor> descriptor1 = characteristic.getDescriptor(
                    UUID.fromString(CommonUUIDUtils.Client_Characteristic_Configuration));
            if (descriptor1.isPresent()) {
                GattDescriptor descriptor = descriptor1.get();
                if (descriptor != null) {
                    byte[] value = descriptor.getValue();
                    if (value != null) {
                        if (!Arrays.equals(value, new byte[]{})) {
                            return true;
                        } else {
                            return false;
                        }
                    }
                }
            }
        }
        return false;
    }

    private String formatTimeYmDhmsf(long time) {
        Timestamp ts = new Timestamp(time);

        return ts.toString();
    }
}
